#ifndef CUDANUMPY_H
#define CUDANUMPY_H

#define NUMBER_OF_CUDAARRAYS 1024
#define RANGE 				   20
//Macro's for use in NumPy
#define PyArray_USECUDA(m) PyArray_CHKFLAGS(m, CNPY_CUDA)
#define PyArray_CUDAUID(obj) (((PyArrayObject *)(obj))->cudauid)
//Member accesors
#define CudaObject_UID(co)(((CudaObject *)(co))->uid)
#define CudaObject_DEVICEDATA(co)(((CudaObject *)(co))->devicedata)
#define CudaObject_STATUS(co)(((CudaObject *)(co))->status)
#define CudaObject_REFERENCECOUNT(co)(((CudaObject *)(co))->referencecount)
//#define CudaObject_STREAM(co, i)(((CudaObject *)(co))->streams[i])
#define CudaObject_STREAMS(co)(((CudaObject *)(co))->streams)
#define CudaObject_SI(co)(((CudaObject *)(co))->si)
#define CudaObject_ACTIVESTREAM(co)(((CudaObject *)(co))->streams[CudaObject_SI(co)])
#define CudaObject_UFUNCS(co)((DeviceUniFuncs *)(((CudaObject *)(co))->ufuncs))
//Helpers
#define CudaObject_INCREF(co)(++((CudaObject *)(co))->referencecount)
#define CudaObject_DECREF(co)(--((CudaObject *)(co))->referencecount)
#define CudaObject_CHECKSTATUS(co, flag) ((((CudaObject *)(co))->status & (flag)) == (flag))
//Status flags
#define DEVICE_ALLOC   0x001 //bit mask: 0001
#define DATA_ON_DEVICE 0x002 //bit mask: 0010
#define DATA_ON_HOST   0x004 //bit mask: 0100

enum cuda_ops {
	//Binary operators
	cuda_add, cuda_sub,
	cuda_div, cuda_mult,
	cuda_max, cuda_min,
	//Unary operators
	cuda_sq, cuda_abs,
	cuda_sqrt,
	//Equality operators
	cuda_leq, cuda_eq, 
	cuda_band, cuda_bor, 
	cuda_unknown
};

typedef struct CudaObject {
	int uid;
	void *devicedata;
	int status;
	int referencecount;
	int *streams; //equivalent to cudaStream_t
	int si; //Stream index, used in ufunc
	void *ufuncs; //void because of circular reference	
} CudaObject;

typedef struct DeviceUniFuncs {
	void (*ufunc_dot_fptr)(CudaObject *A, CudaObject *B, CudaObject *C, int m, int n, int k);
	void (*ufunc_reduce_fptr)(CudaObject*, int, int, char*, void*, int, int);	
	void (*ufunc_m_fptr)(CudaObject*, CudaObject*, int, int, char*, int);
	void (*ufunc_ms_fptr)(CudaObject*, CudaObject*, float*, int, int, char*, int, int);
	void (*ufunc_mm_fptr)(CudaObject*, CudaObject*, CudaObject*, int, int, int, char*, int);
} DeviceUniFuncs;

typedef double cuda_double;
typedef long cuda_long;

#ifndef CUDAFUNC
void ufunc_dot_dbl(CudaObject*, CudaObject*, CudaObject*, int, int, int);
void ufunc_reduce_dbl(CudaObject*, int, int, char*, void*, int, int);
void ufunc_m_dbl(CudaObject*, CudaObject*, int, int, char*, int);
void ufunc_ms_dbl(CudaObject*, CudaObject*, void*, int, int, char*, int, int);
void ufunc_mm_dbl(CudaObject*, CudaObject*, CudaObject*, int, int, int, char*, int);
void ufunc_reduce_lng(CudaObject*, int, int, char*, void*, int, int);
void ufunc_m_lng(CudaObject*, CudaObject*, int, int, char*, int);
void ufunc_ms_lng(CudaObject*, CudaObject*, void*, int, int, char*, int, int);
void ufunc_mm_lng(CudaObject*, CudaObject*, CudaObject*, int, int, int, char*, int);
void ufunc_equality_ms_dbl(CudaObject*, CudaObject*, void*, int, int, char*, int, int);
void ufunc_equality_mm_dbl(CudaObject*, CudaObject*, CudaObject*, int, int, int, char*, int);
void ufunc_equality_ms_lng(CudaObject*, CudaObject*, void*, int, int, char*, int, int);
void ufunc_equality_mm_lng(CudaObject*, CudaObject*, CudaObject*, int, int, int, char*, int);
#endif
#endif
